import requests
import json
import hashlib
import random
from datetime import datetime, timedelta, time
from distutils.util import strtobool
from sqlalchemy.orm import Session
from core.response import resp_500
from core.logger import logger
from app.monitor_metrics.handlers.prom_alarm_record_handler import PromAlarmRecordHandler
from app.manage_notice.models.manage_notice_team import ManageNoticeTeam
from app.monitor_metrics.models.prom_alarm_record import PromAlarmRecord
from app.monitor_metrics.models.prom_department import PromDepartment
from common.utils import CommonTimeUtils
from app.tools_url_shorten.handlers import create_short_link
from common.notice_tools import NoticeTools
from common.welab_k8s.k8s_pod import K8sPod
from setting import config


class PromAlarmCallbackHandler():
    """  alertmanager callback Handler
    """
    def __init__(self):
        self.prometheus_url = config.K8S_PROM_URL
        self.short_url = config.SHORT_URL
        self.prom_alarm_record_handler = PromAlarmRecordHandler()
        self.k8s_pod = K8sPod()
        self.notice_tools = NoticeTools()

    def check_alert_notice_again(self, alert_info, record_info):
        # 告警次数已经超过配置的次数，且需要再次告警的时候
        if alert_info["status"] == "firing" and record_info["notice_count"] >= alert_info["labels"]["i_notice_count"]:
            # 间隔期间:6*5=30分钟，则不触发再次提醒
            if (record_info["number"]-alert_info["labels"]["i_notice_count"]) % 6 != 0:
                logger.info(f"Alarm again at interval time")
                return False

            # # 如果是在晚上休息时间，则不触发再次提醒
            # if CommonTimeUtils.check_between_every_day(time(23,30), time(8,30)) :
            #     logger.info(f"Alarm again at rest time")
            #     return False

            # 需要再次提醒
            return True

        # 不属于再次告警的场景
        return True


    def check_alert_notice_maintain(self, alert_info):
        # 没有维护时间
        if alert_info["labels"]["i_maintain_begin"] == "" or alert_info["labels"]["i_maintain_end"] == "" :
            logger.info(f"maintain is null")
            return True

        # 检查是否在维护时间内
        maintain_begin = CommonTimeUtils.strptime(alert_info["labels"]["i_maintain_begin"])
        maintain_end = CommonTimeUtils.strptime(alert_info["labels"]["i_maintain_end"])
        if strtobool(alert_info["labels"]["i_maintain_repeat"]):
            if CommonTimeUtils.check_between_every_day(maintain_begin.time(), maintain_end.time()) :
                logger.info(f"Alarm in repeat maintain")
                return False
        else:
            if CommonTimeUtils.check_between_datetime(maintain_begin, maintain_end) :
                logger.info(f"Alarm in datetime maintain")
                return False

        logger.info(f"not in maintain")
        return True




    def check_alert_notice(self, db: Session, alert_info: dict, record_info: dict):
        # 告警记录的通知开关是否关闭,恢复的还是要发送
        if not strtobool(str(record_info["state"])) and alert_info["status"] == "firing":
            logger.info(f"state is disenable")
            return False

        # 告警级别为nothing或没有通知人，则不告警
        if alert_info["labels"]["i_level"] == "nothing" or not alert_info["labels"]["i_notice_teams_uuid"]:
            logger.info(f"alarm level is nothing or NO notice_teams")
            return False
        
        # 检查全局告警屏蔽的配置，如果设置了BlockGlobalAlarm告警记录，且state值为0，则屏蔽
        result = PromAlarmRecord.get_object_info_for_block_global(db)
        if result and not strtobool(str(result["state"])):
            logger.info(f"block global alarm state is disenable")
            return False

        # 告警次数大于配置次数后，各个间隔5分钟，6个间隔就是30分钟会再次告警
        if not self.check_alert_notice_again(alert_info, record_info):
            logger.info(f"alarm more than {alert_info['labels']['i_notice_count']} times")
            return False

        # 是否在告警规则的维护时间
        if not self.check_alert_notice_maintain(alert_info):
            logger.info(f"Alarm in maintain")
            return False

        # 可以通知
        logger.info(f"alert can be notice")
        return True


    def is_upgrade_to_department(self, alert_info: dict, record_info: dict):
        # 告警级别为info或者warning，则不升级
        if alert_info["labels"]["i_level"] in ["info","warning"]:
            return False
        
        # 告警时
        if alert_info["status"] == "firing":
            # 告警级别为major，第二次再次告警时（60分钟）及以后，则升级到部门领导
            if alert_info["labels"]["i_level"] == "major" and record_info["notice_count"] > alert_info["labels"]["i_notice_count"]:
                return True
            
            # 告警级别为critical，第一次再次告警（30分钟）及以后，则升级到部门领导
            if alert_info["labels"]["i_level"] == "critical" and record_info["notice_count"] >= alert_info["labels"]["i_notice_count"]:
                return True
            
        # 恢复时
        if alert_info["status"] == "resolved":
            # 告警级别为major，告警已升级后，恢复才发送到部门领导
            if alert_info["labels"]["i_level"] == "major" and record_info["notice_count"] > alert_info["labels"]["i_notice_count"]+1:
                return True
            
            # 告警级别为critical，告警已升级后，恢复才发送到部门领导
            if alert_info["labels"]["i_level"] == "critical" and record_info["notice_count"] > alert_info["labels"]["i_notice_count"]:
                return True
            
        return False

    def alarm_upgrade_to_department(self, db: Session, alert_info: dict, record_info: dict):
        """
        告警升级到部门领导
        """
        notice_teams_uuid = alert_info["labels"]["i_notice_teams_uuid"]

        if self.is_upgrade_to_department(alert_info, record_info):
            # 告警通知人升级
            department_name = alert_info["labels"]["i_department"]
            department_object = PromDepartment.get_object_by_name(db, department_name)
            if not department_object:
                logger.error(f"get department by name:{department_name} failed")
                return notice_teams_uuid
            
            # 添加部门负责人
            department_info = PromDepartment.get_object_info_by_name(db, department_name)
            notice_teams_uuid.extend(department_info["notice_teams_uuid"])
            notice_teams_uuid = list(set(notice_teams_uuid))

            # 通知人有变化，同步到数据
            record_info["notice_teams_uuid"] = notice_teams_uuid
            result = PromAlarmRecord.update(db, record_info)
            if not result:
                logger.error(f"update alarm record:notice_teams_uuid to database failed")

            logger.info(f"alarm upgrade to department success")
        return notice_teams_uuid




    def get_notice_info(self, db: Session, alert_info: dict, record_info: dict):
        """
        获取通知信息
        """
        notice_info = {
            "sms": [],
            "voice": [],
            "mailboxs": [],
            "chat_users": []
        }
        # 告警升级到部门领导
        notice_teams_uuid = self.alarm_upgrade_to_department(db, alert_info, record_info)
        
        # 获取通知人信息
        notice_users = []
        for notice_team_uuid in notice_teams_uuid:
            if not ManageNoticeTeam.get_object_by_uuid(db, notice_team_uuid):
                logger.error(f"get notice team failed, notice_team_uuid:{notice_team_uuid} not exist")
                continue

            notice_team_info = ManageNoticeTeam.get_object_info_by_uuid(db, notice_team_uuid)
            notice_users.extend(notice_team_info["users"])


        # 重组返回数据结构
        for user_info in notice_users:
            chat_user = user_info["mailbox"].split("@")[0]
            if chat_user not in notice_info["chat_users"]:
                notice_info["chat_users"].append(chat_user)
                notice_info["sms"].append(user_info["telephone"])
                notice_info["voice"].append({"username": chat_user, "mobile": user_info["telephone"]})
                notice_info["mailboxs"].append(user_info["mailbox"])

        logger.info(f"get notice info success")
        return notice_info


    def set_short_url(self, db: Session, alert_info: dict):
        alert_info["short_url"] = ""
        if "generatorURL" not in alert_info or "/graph?" not in alert_info["generatorURL"]:
            logger.error("generatorURL is error")
            return False

        alarm_url = f"{self.prometheus_url}graph" + alert_info["generatorURL"].split("/graph")[1]
        short_link = create_short_link(db, alarm_url)
        alert_info["short_url"] = f"{self.short_url}?short_link={short_link}"
        return True



    def send_notice(self, db: Session, alert_info, notice_info, record_info):
        self.set_short_url(db, alert_info)
        notice_msg = self.notice_tools.alarm_message(alert_info, notice_info, record_info)
        notice_msg_markdown = self.notice_tools.alarm_message_markdown(alert_info, record_info)
        notice_msg_card = self.notice_tools.alarm_message_card(alert_info, notice_info, record_info)

        # 发送企业微信到个人，info（通知告警级）别都会发
        result = self.notice_tools.send_wechat(notice_info["chat_users"], notice_msg_markdown, "markdown")
        if not result:
            logger.error(f"send wechat failed, chat_users=>{notice_info['chat_users']}")
        else:
            logger.info(f"send wechat success, chat_users=>{notice_info['chat_users']}")

        # # 发送企业微信小程序的卡片信息，在小程序那边生成工单，warning（一般告警）级别以上
        if alert_info["labels"]["i_level"] in ["warning", "major", "critical"]:
            result = self.notice_tools.send_wechat_card(notice_msg_card)
            if not result:
                logger.error(f"send wechat card failed")
            else:
                logger.info(f"send wechat card success")

        # 发送短信告警，warning（一般告警）级别以上， 并且不是工作时间才发送（节省费用）
        if alert_info["labels"]["i_level"] in ["warning", "major", "critical"] and not CommonTimeUtils.check_now_is_work_time():
            result = self.notice_tools.send_sms(notice_info["sms"], notice_msg)
            if not result:
                logger.error(f"send sms failed, sms=>{notice_info['sms']}")
            else:
                logger.info(f"send sms success, sms=>{notice_info['sms']}")

        # 发送电话告警，major级别以上，只有告警时才打电话
        if alert_info["labels"]["i_level"] in ["major", "critical"] and alert_info["status"] == 'firing':
            result = self.notice_tools.send_voice(notice_info["voice"])
            if not result:
                logger.error(f"send voice failed, voice=>{notice_info['voice']}")
            else:
                logger.info(f"send voice success, voice=>{notice_info['voice']}")

        # 更新通知次数
        record_info["notice_count"] += 1
        result = PromAlarmRecord.update(db, record_info)
        if not result:
            logger.error(f"update alarm record: notice_count failed")
            return False

        logger.info(f"send notice all success =>\n{notice_msg}")
        return True


    def alert_to_notice(self, db: Session, alert_info: dict, record_info: dict):
        # 检查是否需要通知
        if not self.check_alert_notice(db, alert_info, record_info):
            logger.info(f"alert not to notice ")
            return True

        # 获取通知人的信息
        notice_info = self.get_notice_info(db, alert_info, record_info)
        if not notice_info["chat_users"]:
            logger.error(f"get notice info is None")
            return False

        # 发送告警信息
        result = self.send_notice(db, alert_info, notice_info, record_info)
        if not result:
            logger.info(f"send notice failed")
            return False

        logger.info(f"alert to notice success")
        return True


    def create_comment_info(self, record_info: dict, alert_info: dict):
        handler_info = {
            "运维": [
                "boll.lai已手动处理",
                "sumi.wang已手动处理",
            ],
            "数据库": [
                "caitlyn.liu已手动处理",
                "charles.fang已手动处理"
            ],
            "大数据": [
                "charles.fang已手动处理"
            ],
            "信息安全": [
                "sam.liang已手动处理",
                "emmitt.cheng已手动处理"
            ],
            "商业智能": [
                "charles.fang已手动处理"
            ],
            "数据平台": [
                "hongkai.wang已手动处理",
                "yufei.liu已手动处理"
            ]
        }

        if alert_info["status"] != "resolved":
            return True

        if record_info["comment"] != "":
            return True

        # 告警和恢复间隔3分钟内，认为是自动恢复
        # starts_at_timestamp = datetime.timestamp(record_info["starts_at"] + timedelta(minutes=3))
        # ends_at_timestamp = datetime.timestamp(record_info["ends_at"])
        # if starts_at_timestamp > ends_at_timestamp:
        # 告警三次内，认为是自动恢复
        if record_info["number"] <= 3:
            record_info["comment"] = "故障自愈:无处理"
            return True

        # 随机填写处理人
        handler_user = handler_info.get(alert_info["labels"]["i_department"], handler_info["运维"])
        record_info["comment"] =random.choice(handler_user)

        return True




    def alert_to_database(self, db: Session, alert_info: dict):
        """
        告警信息写入数据库
        """
        record_info = PromAlarmRecord.get_object_info_by_fingerprint(db, alert_info["labels"]["alertname"], alert_info["fingerprint"])
        if not record_info:
            # 告警记录在数据中中存在，则更新
            if alert_info["status"] == "resolved":
                # firing可以处理多次, resolved只处理一次。
                logger.error(f"was resolved")
                return False

            # 在数据库中新建一条记录
            data = {
                "name": alert_info["labels"]["alertname"],
                "classes": alert_info["labels"]["i_classes"],
                "target": alert_info["labels"]["i_target"],
                "status": alert_info["status"],
                "state": 1,
                "number": 1,
                "fingerprint": alert_info["fingerprint"],
                "notice_teams_uuid": alert_info["labels"]["i_notice_teams_uuid"],
                "notice_count": 0,
                "labels": alert_info["labels"],
                "annotations": alert_info["annotations"],
                "starts_at": CommonTimeUtils.strptime(alert_info["startsAt"], True),
                "ends_at": CommonTimeUtils.strptime(alert_info["endsAt"], True),
                "comment": ""
            }
            record_info = PromAlarmRecord.add(db, data)
            if not record_info:
                logger.error(f"alarm record add to database failed")
                return False
            record_info = record_info.object_to_dict(db)
            logger.info(f"alarm record add to database success")
        else:
            # 存在告警状态的记录， 更新状态
            record_info["number"] += 1
            record_info["starts_at"] = CommonTimeUtils.strptime(alert_info["startsAt"], True)
            record_info["ends_at"] = CommonTimeUtils.strptime(alert_info["endsAt"], True)
            record_info["status"] = alert_info["status"]
            record_info.pop("update_at")
            self.create_comment_info(record_info, alert_info)
            result = PromAlarmRecord.update(db, record_info)
            if not result:
                logger.error(f"update alarm record to database failed")
                return False
            logger.info(f"alarm record update to database success")

        return record_info


    def set_default_value(self, db: Session, alert_info: dict):
        """
        设置默认值，兼容之前的告警规则
        """
        labels_default = {
            "i_classes": "k8s容器",
            "i_maintain_begin": "",
            "i_maintain_end": "",
            "i_maintain_repeat": "0",
            "i_level": "info",
            "i_notice_count": 1,
            "i_notice_teams_uuid": [],
            "i_department": "运维",
            "i_callback": "",
            "i_target": ""
        }
        for key, value in labels_default.items():
            if key not in alert_info["labels"] or alert_info["labels"][key].strip() == "":
                alert_info["labels"][key] = value


        if not alert_info["labels"]["i_notice_teams_uuid"]:
            # 默认的通知组，为运维部门的负责人
            department_info = PromDepartment.get_object_info_by_name(db, "运维")
            if department_info["notice_teams_uuid"]:
                alert_info["labels"]["i_notice_teams_uuid"] = department_info["notice_teams_uuid"]
        else:
            alert_info["labels"]["i_notice_teams_uuid"] = json.loads(alert_info["labels"]["i_notice_teams_uuid"])


        alert_info["labels"]["i_notice_count"] = int(alert_info["labels"]["i_notice_count"])

        # 告警指纹添加告警启动时间
        # fingerprint_md5 = hashlib.md5(str(alert_info['fingerprint'] + alert_info["startsAt"]).encode("utf-8"))
        # alert_info['fingerprint'] = fingerprint_md5.hexdigest()

        logger.info(f"set default value success")
        return True



    def alert_to_callback(self, alert_info: dict, record_info: dict):
        """
        回调告警规则上配置的回调接口
        """
        url = alert_info["labels"]["i_callback"].strip()
        headers = {
            "Content-Type": "application/json",
        }

        if url == "":
            logger.info(f"callback url is null")
            return True

        if record_info["number"] > 1 and alert_info["status"] == "firing":
            logger.info(f"callback url already called")
            return True

        req_result = True
        try:
            # 回调接口
            alert_info["uuid"] = record_info["uuid"]
            req = requests.post(url=url, headers=headers, data=json.dumps(alert_info), timeout=5)
            if req.status_code != 200:
                logger.error(f"callback url: {url} failed ")
                req_result = False
        except Exception as e:
            logger.exception(f"callback url: {url} error: {e}")
            req_result = False

        # 执行回调接口失败，需要通知到企业微信群
        if not req_result:
            notice_msg = f"执行回调接口: {url} 失败 "
            self.notice_tools.send_wechat_group(notice_msg)
            return False

        logger.info(f"callback url: {url} success")
        return True



    def handle_alerts(self, db: Session, data: dict):
        try:
            logger.info(f"收到告警信息=>{data}")
            for alert_info in data["alerts"]:
                # 设置默认值，兼容
                self.set_default_value(db, alert_info)

                # 更新数据库中的记录信息
                record_info = self.alert_to_database(db, alert_info)
                if not record_info:
                    logger.error(f"alert to database failed, alert_info=>{alert_info}")
                    continue

                # 发送告警信息
                self.alert_to_notice(db, alert_info, record_info)

                # 执行告警回调
                self.alert_to_callback(alert_info, record_info)

                logger.info(f"处理完成:{alert_info}")

        except Exception as e:
            message = f"alertmanager callback 500 error"
            logger.exception(f"{message} => {e}")
            return resp_500(message)

        return True



    def get_alarm_status(self, db: Session):
        try:
            watchdog_info = PromAlarmRecord.get_object_info_for_watchdog(db)
            if not watchdog_info:
                logger.error(f"watchdog not exist")
                return False

            firing_infos = PromAlarmRecord.get_object_info_for_firing(db)
            now_timestamp = datetime.now().timestamp()
            for firing_info in firing_infos:
                firing_timestamp = datetime.timestamp(CommonTimeUtils.strptime(firing_info["update_at"]) + timedelta(minutes=15))
                if firing_timestamp < now_timestamp:
                    logger.error(f"no alarm last 10 minutes => {firing_info}")
                    return False
        except Exception as e:
            message = f"get alarm status error"
            logger.exception(f"{message} => {e}")
            return resp_500(message)

        return True






